﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Reflection;
using System.Linq.Expressions;

namespace UIT.Utils
{
    public static class TypeUtil
    {
        public static TypeConverter GetTypeConverter(this object value)
        {
#if !SILVERLIGHT
            if (value != null)
                return TypeDescriptor.GetConverter(value);
#endif
            return null;
        }

        public static IEnumerable<TypeConverter> GetTypeConverters(this object value, ParameterInfo targetParameter)
        {
            List<TypeConverter> converters = new List<TypeConverter>();

            converters.Add(GetTypeConverter(value));

            var possibleTypeConverters = (TypeConverterAttribute[])targetParameter.GetCustomAttributes(typeof(TypeConverterAttribute), true);
            if (possibleTypeConverters != null)
            {
                foreach (TypeConverterAttribute converterAttribute in possibleTypeConverters)
                    converters.Add((TypeConverter)Activator.CreateInstance(Type.GetType(converterAttribute.ConverterTypeName)));
            }

            return converters;
        }

        public static IEnumerable<TypeConverter> GetTypeConverters(this object value, PropertyInfo targetProperty)
        {
            List<TypeConverter> converters = new List<TypeConverter>();

            converters.Add(GetTypeConverter(value));

            var possibleTypeConverters = (TypeConverterAttribute[])targetProperty.GetCustomAttributes(typeof(TypeConverterAttribute), true);
            if (possibleTypeConverters != null)
            {
                foreach (TypeConverterAttribute converterAttribute in possibleTypeConverters)
                    converters.Add((TypeConverter)Activator.CreateInstance(Type.GetType(converterAttribute.ConverterTypeName)));
            }

            return converters;
        }

        public static bool TryConvert(this object value, PropertyInfo targetProperty, out object convertedValue)
        {
            if (!value.NeedConversion(targetProperty.PropertyType))
            {
                convertedValue = value;
                return true;
            }

            foreach (TypeConverter converter in value.GetTypeConverters(targetProperty))
            {
                if (converter != null && converter.CanConvertTo(targetProperty.PropertyType))
                {
                    convertedValue = converter.ConvertTo(value, targetProperty.PropertyType);
                    return true;
                }
            }
            convertedValue = null;
            return false;
        }

        public static bool TryConvert(this object value, ParameterInfo targetProperty, out object convertedValue)
        {
            if (!value.NeedConversion(targetProperty.ParameterType))
            {
                convertedValue = value;
                return true;
            }

            foreach (TypeConverter converter in value.GetTypeConverters(targetProperty))
            {
                if (converter.CanConvertTo(targetProperty.ParameterType))
                {
                    convertedValue = converter.ConvertTo(value, targetProperty.ParameterType);
                    return true;
                }
            }
            convertedValue = null;
            return false;
        }

        public static object Convert(this object value, PropertyInfo targetProperty, bool throwException = false)
        {
            object result;
            if (!TryConvert(value, targetProperty, out result) && throwException)
                throw new InvalidCastException(string.Format("Could not convert {0} to {1}", value, targetProperty.PropertyType));

            return result;
        }

        public static bool NeedConversion(this object value, Type targetType)
        {
            return !targetType.IsInstanceOfType(value);
        }

        /// <summary>
        /// Builds a Delegate instance from the supplied MethodInfo object and a target to invoke against.
        /// </summary>
        public static Delegate ToDelegate(this MethodInfo mi, object target)
        {
            if (mi == null)
                throw new ArgumentNullException("mi");

            Type delegateType;

            var typeArgs = mi.GetParameters()
                .Select(p => p.ParameterType)
                .ToList();

            // builds a delegate type
            if (mi.ReturnType == typeof(void))
            {
                delegateType = Expression.GetActionType(typeArgs.ToArray());

            }
            else
            {
                typeArgs.Add(mi.ReturnType);
                delegateType = Expression.GetFuncType(typeArgs.ToArray());
            }

            // creates a binded delegate if target is supplied
            var result = (target == null)
                ? Delegate.CreateDelegate(delegateType, mi)
                : Delegate.CreateDelegate(delegateType, target, mi);

            return result;
        }
    }
}
